﻿using System;
using System.Collections;
using XNA = Microsoft.Xna.Framework.Graphics;

using Heaven.Mathematics;
using Heaven.Graphics;
using Heaven.Geometry;
using Color=Heaven.Graphics.Color;
using VertexElement=Heaven.Graphics.VertexElement;

namespace Heaven.Xna.Graphics
{
    /// <summary>
    /// Реализация графического 
    /// устройства средствами XNA
    /// </summary>
    public abstract class PureDevice : IDevice
    {
        #region Events

        /// <summary>
        /// Event occures when device is needing to 
        /// render its content (for ex., when 
        /// inner buffer has changed size)
        /// </summary>
        public event EventHandler RenderRequest;

        /// <summary>
        /// Occurs when IsAvailable property has been changed
        /// </summary>
        public event EventHandler AvailabilityChanged;

        #endregion

        #region Атрибуты
        
        // Is device available?
        bool available = true;

        // Цвет по умолчанию
        XNA.Color defaultColor = XNA.Color.White;
        // Значение глубины по умолчанию
        double defaultDepth = 1.0f;
        // Текущие вершинные буферы
        // XNA.VertexBuffer[] currentVertexBuffer = new XNA.VertexBuffer[256];

        // Caches device representaions of polygonal objects
        PolygonalManager polygonalManager = new PolygonalManager();

        /// <summary>
        /// Caches device representaions of texture objects
        /// </summary> 
        protected TextureManager textureManager = null;

        /// <summary>
        /// Caches shader objects
        /// </summary>
        internal ShaderManager shaderManager = null;

        /// <summary>
        /// Current setted render target
        /// </summary>
        protected RenderTarget currentRenderTarget = null;

        /// <summary>
        /// Графическое устройство XNA
        /// </summary>
        protected XNA.GraphicsDevice device = null;
        /// <summary>
        /// Возможности данного графического устройства
        /// </summary>
        protected Capabilities capabilities = new Capabilities();
        /// <summary>
        /// Состояния рендера
        /// </summary>
        protected IRenderStates renderStates = null;
        
        /* // <summary>
        /// Текущая декларация текстур
        /// </summary>
        // protected IVertexDeclaration currentVertexDeclaration = null;
        /// <summary>
        /// Текущий буфер индексов
        /// </summary>
        // protected HardwareBuffer currentIndexBuffer = null;*/
        /// <summary>
        /// Компилятор шейдерных эффектов
        /// </summary>
        protected IShaderCompiler shaderCompiler = null;

        #endregion

        #region Свойства

        /// <summary>
        /// Is device available?
        /// </summary>
        public bool IsAvailable
        {
            get { return available; }
        }

        /// <summary>
        /// Возможности этого устройства
        /// </summary>
        public Capabilities Capabilites 
        {
            get { return capabilities; } 
        }

        /// <summary>
        /// Доступ к режимам устройства
        /// </summary>
        public IRenderStates RenderStates
        {
            get { return renderStates; }
        }

        /// <summary>
        /// Цвет для очистки буфера
        /// </summary>
        public Color DefaultColor 
        {
            get { return Converter.Convert(defaultColor); }
            set { defaultColor = Converter.Convert(value); } 
        }

        /// <summary>
        /// Значение используемое для очистки z-буфера
        /// </summary>
        public double DefaultDepth 
        {
            get { return defaultDepth; }
            set { defaultDepth = value; }
        }

        /// <summary>
        /// Текстура, куда осуществляеся вывод кадра
        /// </summary>
        public virtual RenderTarget RenderTarget 
        {
            get 
            {
                return currentRenderTarget;
            }
            set
            {
                // Resolving previous render target
                if(currentRenderTarget != null)
                {
                    textureManager.GetRenderTarget(currentRenderTarget).EndRendering();
                    currentRenderTarget.Invalidate();
                }
                if (value == null)
                {
                    currentRenderTarget = null;
                    return;
                }

                currentRenderTarget = value;
                textureManager.GetRenderTarget(currentRenderTarget).BeginRendering();
            } 
        }


        /// <summary>
        /// Текущее разрешение внутреннего буфера
        /// </summary>
        public abstract Size Resolution { get; set; }

        #endregion

        #region Initialization

        /// <summary>
        /// Default constructor
        /// </summary>
        protected PureDevice()
        {
            
        }

        #endregion

        #region Щейдеры

        /// <summary>
        /// Компилятор шейдеров
        /// </summary>
        public IShaderCompiler ShaderCompiler
        {
            get
            {
                if (shaderCompiler == null)
                {
                    XNA.EffectPool effectpool = new XNA.EffectPool();
                    shaderCompiler = new ShaderCompiler(device, effectpool, textureManager);
                }
                return shaderCompiler;
            }
        }

        #endregion

        #region Рендеринг

        /// <summary>
        /// Request for rendering
        /// </summary>
        protected void RequestRendering()
        {
            if (RenderRequest != null) RenderRequest(this, EventArgs.Empty);
        }

        /// <summary>
        /// Begins rendering
        /// </summary>
        public void Begin()
        {            
        }

        /// <summary>
        /// Закночить вывод сцены и вывести ее на экран
        /// </summary>
        public virtual void Present()
        {
            if(currentRenderTarget != null)
            {
                throw new Exception("There is no way to perform Present operation. You have to end redering to the render target");
            }
            
            device.Present();
        }

        /// <summary>
        /// Очистка заднего буфера
        /// </summary>
        public void Clear()
        {
            device.Clear(XNA.ClearOptions.Target | XNA.ClearOptions.DepthBuffer, defaultColor, (float)defaultDepth, 0);
        }

        /// <summary>
        /// Очистка только буфера глубины
        /// </summary>
        public void ClearDepth()
        {
            device.Clear(XNA.ClearOptions.DepthBuffer, defaultColor, (float)defaultDepth, 0);
        }

        /// <summary>
        /// Очистка только цвета пикселей
        /// </summary>
        public void ClearFrame()
        {
            device.Clear(XNA.ClearOptions.Target, defaultColor, (float)defaultDepth, 0);
        }

        /*/// <summary>
        /// Отрисовка полигонов, находящихся в вершинном буфере
        /// </summary>
        /// <param name="startVertex">Вершина первого примитива</param>
        /// <param name="primitiveCount">Количество примитивов</param>
        public void Draw(int startVertex, int primitiveCount)
        {
            if (primitiveCount != 0)
            {
                device.DrawPrimitives(XNA.PrimitiveType.TriangleList, startVertex, primitiveCount);
            }
        }

        /// <summary>
        /// Отрисовка полигонов, используя индексный буфер
        /// </summary>	
        /// <param name="baseIndex">Базовый индекс (realIndex = Index + baseIndex)</param>	
        /// <param name="minVertex">Минимальная вершина</param>
        /// <param name="numVertices">Количество вершин</param>
        /// <param name="startIndex">Начальный индекс</param>		
        /// <param name="primitiveCount">Количество примитовов</param>
        public void DrawIndexed(int baseIndex, int minVertex, int numVertices, int startIndex, int primitiveCount)
        {
            if (primitiveCount != 0)
            {
                device.DrawIndexedPrimitives(
                    XNA.PrimitiveType.TriangleList, 
                    baseIndex, minVertex, numVertices,
                    startIndex, primitiveCount);
            }
        }*/

        /// <summary>
        /// Draws a polygonal object
        /// </summary>
        /// <param name="polygonal">Polygonal object</param>
        /// <param name="shader">Shader</param>
        public void Draw(Polygonal polygonal, Shader shader)
        {
            if (polygonal.IsEmpty) return;
            XNA.Effect effect = shaderManager.Get(shader);
            XNA.EffectTechnique technique = effect.CurrentTechnique;
            effect.Begin();
            try
            {
                for (int i = 0; i < effect.CurrentTechnique.Passes.Count; i++)
                {
                    technique.Passes[i].Begin();
                    try
                    {
                        polygonalManager.Draw(device, polygonal);
                    }
                    finally
                    {
                        technique.Passes[i].End();
                    }
                }
            }
            finally
            {
                effect.End();
            }
        }

        #endregion

        #region Деструктор

        /// <summary>
        /// Деструктор
        /// </summary>
        ~PureDevice()
        {
            if(device != null) device.Dispose();
        }

        /// <summary>
        /// Force disposing this device immediately 
        /// </summary>
        public virtual void Dispose()
        {
            GC.SuppressFinalize(this);
            if (device != null) device.Dispose();
        }

        #endregion


        #region Shading

        /// <summary>
        /// Finds the parameter by semantic name
        /// </summary>
        /// <param name="shader">Shader</param>
        /// <param name="semantic">Semantic</param>
        /// <returns>Parameter name or null if the parameter is not found</returns>
        public string GetParameterBySemantic(Shader shader, string semantic)
        {
            XNA.Effect effect = shaderManager.Get(shader);
            XNA.EffectParameter parameter = effect.Parameters.GetParameterBySemantic(semantic);
            return parameter == null ? null : parameter.Name;
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, bool value)
        {
            XNA.Effect effect = shaderManager.Get(shader);
            XNA.EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            parameter.SetValue(value);
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, bool[] value)
        {
            XNA.Effect effect = shaderManager.Get(shader);
            XNA.EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            parameter.SetValue(value);
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, float value)
        {
            XNA.Effect effect = shaderManager.Get(shader);
            XNA.EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            parameter.SetValue((float)value);
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, float[] value)
        {
            XNA.Effect effect = shaderManager.Get(shader);
            XNA.EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            float[] copy = new float[value.Length];
            for (int i = 0; i < value.Length; i++) copy[i] = (float)value[i];
            parameter.SetValue(copy);
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, int value)
        {
            XNA.Effect effect = shaderManager.Get(shader);
            XNA.EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            parameter.SetValue(value);
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, int[] value)
        {
            XNA.Effect effect = shaderManager.Get(shader);
            XNA.EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            parameter.SetValue(value);
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, Heaven.Texturing.Texture value)
        {
            XNA.Effect effect = shaderManager.Get(shader);
            XNA.EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            parameter.SetValue(textureManager.Get(value));
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, Color value)
        {
            XNA.Effect effect = shaderManager.Get(shader);
            XNA.EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            parameter.SetValue(Converter.ConvertToVector4(value));
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, Color[] value)
        {
            XNA.Effect effect = shaderManager.Get(shader);
            XNA.EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            Microsoft.Xna.Framework.Vector4[] colors = new Microsoft.Xna.Framework.Vector4[value.Length];
            for (int i = 0; i < value.Length; i++)
            {
                colors[i] = Converter.ConvertToVector4(value[i]);
            }
            parameter.SetValue(colors);
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, Matrix4 value)
        {
            XNA.Effect effect = shaderManager.Get(shader);
            XNA.EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            parameter.SetValue(Converter.Convert(value));
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, Matrix4[] value)
        {
            XNA.Effect effect = shaderManager.Get(shader);
            XNA.EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            Microsoft.Xna.Framework.Matrix[] matrices = new Microsoft.Xna.Framework.Matrix[value.Length];
            for (int i = 0; i < value.Length; i++)
            {
                matrices[i] = Converter.Convert(value[i]);
            }
            parameter.SetValue(matrices);
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, Vector4 value)
        {
            XNA.Effect effect = shaderManager.Get(shader);
            XNA.EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            parameter.SetValue(Converter.Convert(value));
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, Vector3 value)
        {
            XNA.Effect effect = shaderManager.Get(shader);
            XNA.EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            parameter.SetValue(Converter.Convert(value));
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, Vector4[] value)
        {
            XNA.Effect effect = shaderManager.Get(shader);
            XNA.EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            Microsoft.Xna.Framework.Vector4[] vecs = new Microsoft.Xna.Framework.Vector4[value.Length];
            for (int i = 0; i < value.Length; i++)
            {
                vecs[i] = Converter.Convert(value[i]);
            }
            parameter.SetValue(vecs);
        }

        #endregion

    }
}
